package com.gmrz.service.logpool;

import com.gmrz.service.challengestore.ChallengeStoreException;
import com.gmrz.uaf.common.Constants;
import com.gmrz.uaf.db.DAOException;
import com.gmrz.uaf.protocol.v1.schema.FidoLog;
import com.gmrz.uaf.server.ServerConfig;
import com.gmrz.util.Strings;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Created by chentaikun on 16/12/28.
 */
@Singleton
public class LogPoolManager {

    private static final Logger LOG = LogManager.getLogger(LogPoolManager.class);
    // 阻塞队列 的最大容量
    private int MAX_QUEUE_SIZE=100;
    // 批量进行插入的数量
    private int BATCH_SIZE=10;
    // 线程睡眠时间
    private int SLEEP_TIME=500;
    // 日志插入执行的最大的时间间隔
    private long MAX_EXE_TiME = 10000;
    // 创建一个单线程的线程池
    private ExecutorService logManagerThreadPool = Executors.newSingleThreadExecutor();
    // 创建一个定长的线程池，线程数量为（java虚拟机可用的处理器数量 * 2 + 20 ）
    private ExecutorService logWorkerThreadPool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2 + 20);
    // 阻塞队列，存放日志内容
    private BlockingQueue<FidoLog> queue;
    // Boolean 原子变量类
    private AtomicBoolean run = new AtomicBoolean(true);
    // 整型 原子变量类，记录阻塞队列中的任务数量
    private AtomicInteger logCount = new AtomicInteger(0);
    private ServerConfig serverConfig;
    private long lastExecuteTime;
    @Inject
    public void setServerConfig(ServerConfig config) {
        this.serverConfig = config;
    }
    public void init(){
        MAX_QUEUE_SIZE = serverConfig.getLogQueueMaxsize()>0?serverConfig.getLogQueueMaxsize():MAX_QUEUE_SIZE;
        BATCH_SIZE = serverConfig.getLogBatchMaxsize()>0?serverConfig.getLogBatchMaxsize():BATCH_SIZE;
        SLEEP_TIME = serverConfig.getLogWatchMillionSeconds()>0?serverConfig.getLogWatchMillionSeconds():SLEEP_TIME;
        // 基于链表的双向阻塞队列，在队列的两端都可以插入和移除元素，是线程安全的，多线程并发下效率更高
        queue = new LinkedBlockingDeque<FidoLog>(MAX_QUEUE_SIZE);
        lastExecuteTime = System.currentTimeMillis();
        logManagerThreadPool.execute(new Runnable() {
            public void run() {
                while(run.get()){
                    try {
                        Thread.sleep(SLEEP_TIME);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    // 满足存放了10个日志  或  满足时间间隔已经大于设置的最大时间间隔时  执行日志插入
                    if(logCount.get()>=BATCH_SIZE || (System.currentTimeMillis() - lastExecuteTime) > MAX_EXE_TiME){
                        if(logCount.get() > 0) {
                            LOG.info("begin drain log queue to database...");
                            List<FidoLog> list = new ArrayList<FidoLog>();
                            /**
                             *  drainTo (): 一次性从BlockingQueue获取所有可用的数据对象（还可以指定获取数据的个数），
                             *  通过该方法，可以提升获取数据效率；不需要多次分批加锁或释放锁。
                             *  将取出的数据放入指定的list集合中
                             */
                            queue.drainTo(list);
                            logCount.set(0);
                            // 从线程池中取出线程执行日志插入
                            logWorkerThreadPool.execute(new LogWorker(list));
                            LOG.info("end drain log queue to database...");
                        }
                        lastExecuteTime = System.currentTimeMillis();
                    }
                }
                LOG.info("LogPoolManager shutdown successfully");
            }
        });
    }

    public void addLog(FidoLog fidoLog){
        boolean logSwitch = true;
        try {
            String value = serverConfig.getUasConfig(Constants.UAF_FIDO_LOG_SWITCH);
            if(Strings.isNotBlank(value)){
                logSwitch = Boolean.parseBoolean(value);
            }
        } catch (Exception e) {
            LOG.error("get uas config error",e);
        }
        if(!logSwitch){
            return;
        }

        if(logCount.get()>=MAX_QUEUE_SIZE){
            LOG.error("Log count exceed log queue's max size,will be log in file");
            LOG.error(fidoLog);
            return;
        }else if(LOG.isDebugEnabled()){
            LOG.debug(fidoLog);
        }
        // 将数据放入阻塞队列中，放入成功返回true
        this.queue.offer(fidoLog);
        logCount.incrementAndGet();
    }

    public void shutdown(){
        LOG.info("LogPoolManager shutdown...");
        run.set(false);
        logWorkerThreadPool.shutdownNow();
        logManagerThreadPool.shutdownNow();
    }
}
